import configparser
import copy
import csv
import datetime
import json
import logging
import os
import re
import sys
import time
import zipfile
from threading import Thread

import pandas as pd
import psutil
from bypy import ByPy

import Global_param as g
from CTP_API import thosttraderapi as tdapi
from CTP_API import thostmduserapi as mdapi
from UserStruct import *
import tushare as ts
from printFunction import *
from ws_server import postMessage, marketData2dict


# 订阅全部行情
def subscribeMarketData():
    timePrint('开始订阅行情')
    for i in range(0, len(g.subID), 10):
        if i + 10 <= len(g.subID):
            sub(g.subID[i:i + 10])
        else:
            sub(g.subID[i:])
        time.sleep(0.1)
    timePrint('已发送全部订阅请求')


# 订阅少量行情
def sub(subID_list):
    ret = g.mduserapi.SubscribeMarketData([id.encode('utf-8') for id in subID_list], len(subID_list))
    if ret == 0:
        timePrintLog('发送订阅{}合约请求成功！'.format(str(subID_list)))
    else:
        timePrintLog('发送订阅{}合约请求失败！'.format(str(subID_list)))
        judge_ret(ret)
        while ret != 0:
            queryFile = tdapi.CThostFtdcQryProductField()
            ret = g.mduserapi.SubscribeMarketData([id.encode('utf-8') for id in subID_list], len(subID_list))
            timePrintLog('正在订阅{}行情...'.format(str(subID_list)))
            time.sleep(1)


# 获取tick并传递
def get_tick():
    while True:
        try:
            pDepthMarketData = g.tickQueue.get()
            postMessage(marketData2dict(pDepthMarketData))
        except Exception as e:
            red_printLog("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$")
            red_printLog(e)


# 如果是无效数据，则返回True
def clean_data(pDepthMarketData):
    if '7*24' not in g.broker_name:
        today = datetime.datetime.now().strftime('%Y-%m-%d')
        stamp = today + " " + pDepthMarketData.UpdateTime
        timeArray = time.strptime(stamp, "%Y-%m-%d %H:%M:%S")
        timeStamp = int(time.mktime(timeArray))
        now = int(time.time())
        now_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        # 时间戳之差超过1分钟即认为是无效数据
        if abs(now - timeStamp) > 60:
            timePrintLog(f"marketdata delay : ID:{pDepthMarketData.InstrumentID}, Stamp:{stamp}, Now:{now_time}")
            return True
        else:
            return False


# 判断需要给哪些策略传tick，以及哪些合约需要合成min1 K线
def distribute_tick(pDepthMarketData):
    # # 交易账户登录成功后，传递数据
    # if not g.tdLogin_flag:
    #     return
    for strategy in g.strategy_map.values():
        if pDepthMarketData.InstrumentID in strategy.subID:
            t1 = Thread(target=save_tick, args=(strategy, pDepthMarketData,))
            t1.start()

    # 如果策略订阅了K线
    if pDepthMarketData.InstrumentID in g.subKlineID:
        # timePrintLog()('合成K线')
        t2 = Thread(target=tick_to_Kline, args=(pDepthMarketData,))
        t2.start()
        # tick_to_Kline(pDepthMarketData)


def save_tick(strategy, pDepthMarketData):
    # 上锁
    instrumentID = pDepthMarketData.InstrumentID
    strategy.specific_strategy_map[instrumentID].market_data_lock.acquire()

    strategy.specific_strategy_map[instrumentID].market_data = copy.copy(pDepthMarketData)
    # 解锁
    # strategy.specific_strategy_map[instrumentID].market_data_lock.release()

    # 调用策略中的行情事件
    t2 = Thread(target=strategy.specific_strategy_map[instrumentID].onQuote)
    t2.start()


# tick合成为K线
def tick_to_Kline(pDepthMarketData):
    instrumentID = pDepthMarketData.InstrumentID
    # if instrumentID == 'm2209':
    # timePrintLog()(pDepthMarketData.UpdateTime + '.' + str(pDepthMarketData.UpdateMillisec))
    # timePrintLog()(pDepthMarketData.LastPrice)

    st = pDepthMarketData.UpdateTime.split(':')
    # 如果tick的分钟数 等于K线的分钟数，则不是新的分钟线
    if int(st[1]) == g.klineMin_map[instrumentID].updateTime.minute:
        newMinitue = False
    else:
        newMinitue = True

        # 防止开启程序后第一次推送
        if g.klineMin_map[instrumentID].instrumentID != '':
            # timePrintLog()(pDepthMarketData.InstrumentID)
            # print_object(g.klineMin_map[pDepthMarketData.InstrumentID])
            # g.klineMin_map[instrumentID].closePrice = pDepthMarketData.LastPrice

            # 注意Volume字段是累计成交量，所以这个时间段内成交量为该值与上一时间段末成交量的差值
            # 成交量 = max（当前累计成交 - 上一刻成交， 0）
            g.klineMin_map[instrumentID].volume = max(pDepthMarketData.Volume - g.klineMin_map[instrumentID].lastVolume,
                                                      0)
            g.klineQueue.put(copy.deepcopy(g.klineMin_map[instrumentID]))
    # 如果是新1分钟，生成一个新k线变量，CBarData结构体中有OHLC,time等K线字段
    if newMinitue:
        g.klineMin_map[instrumentID] = BarData()
        g.klineMin_map[instrumentID].barType = bt.min
        g.klineMin_map[instrumentID].instrumentID = instrumentID
        g.klineMin_map[instrumentID].updateTime = datetime.time(int(st[0]), int(st[1]), 0, 0)
        g.klineMin_map[instrumentID].volume = 0
        g.klineMin_map[instrumentID].openInterest = pDepthMarketData.OpenInterest
        g.klineMin_map[instrumentID].openPrice = pDepthMarketData.LastPrice
        g.klineMin_map[instrumentID].highPrice = pDepthMarketData.LastPrice
        g.klineMin_map[instrumentID].lowPrice = pDepthMarketData.LastPrice
        g.klineMin_map[instrumentID].closePrice = pDepthMarketData.LastPrice

        g.klineMin_map[instrumentID].lastVolume = pDepthMarketData.Volume
    else:
        # 如果不是新1分钟，更新相关数据
        g.klineMin_map[instrumentID].highPrice = max(g.klineMin_map[instrumentID].highPrice, pDepthMarketData.LastPrice)
        g.klineMin_map[instrumentID].lowPrice = min(g.klineMin_map[instrumentID].lowPrice, pDepthMarketData.LastPrice)
        g.klineMin_map[instrumentID].closePrice = pDepthMarketData.LastPrice
        # 持仓量
        g.klineMin_map[instrumentID].openInterest = pDepthMarketData.OpenInterest
    # # 注意Volume字段是累计成交量，所以这个时间段内成交量为该值与上一时间段末成交量的差值
    # # 只会在创建新K线的时候调用
    # if not g.klineMin_map[instrumentID].lastVolume:
    #     # 成交量 = max（当前累计成交 - 上一刻成交， 0）
    #     g.klineMin_map[instrumentID].volume = max(pDepthMarketData.Volume - g.klineMin_map[instrumentID].lastVolume, 0)
    #     g.klineMin_map[instrumentID].lastVolume = pDepthMarketData.Volume


# 获取K线并传递
def get_Bar():
    while True:
        kline = g.klineQueue.get()
        # 订阅合约成功后会有几个合约名为空的合约，需清理掉
        if str(kline.instrumentID) == '':
            continue
        # 如果不是7*24小时数据，需要进行数据清理
        if '7*24' not in g.broker_name:
            today = datetime.datetime.now().strftime('%Y-%m-%d')
            stamp = today + " " + str(kline.updateTime)
            timeArray = time.strptime(stamp, "%Y-%m-%d %H:%M:%S")
            timeStamp = int(time.mktime(timeArray))
            now = int(time.time())
            now_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            # 时间戳之差超过1分钟即认为是无效数据
            if abs(now - timeStamp) > 60:
                timePrintLog(f"marketdata delay : ID:{kline.instrumentID}, Stamp:{stamp}, Now:{now_time}")
                continue
        # if kline.InstrumentID == 'FG209':
        #     g.start = time.time()
        t3 = Thread(target=distribute_Kline, args=(kline,))
        # timePrintLog()(id(t3))
        t3.start()


# 判断需要给哪些策略传Kline
def distribute_Kline(kline):
    # # 交易账户登录成功后，传递数据
    # if not g.tdLogin_flag:
    #     return
    for strategy in g.strategy_map.values():
        if kline.instrumentID in strategy.subID and kline.barType in strategy.subKlineType:
            t1 = Thread(target=save_Kline, args=(strategy, kline,))
            t1.start()


def save_Kline(strategy, kline):
    instrumentID = kline.instrumentID
    strategy.specific_strategy_map[instrumentID].kline_lock.acquire()

    strategy.specific_strategy_map[instrumentID].barData = kline
    # timePrintLog()('调用策略{}'.format(strategy.strategyID))

    # 调用策略中的行情事件
    t2 = Thread(target=strategy.specific_strategy_map[instrumentID].onBar, )
    t2.start()


def insertOrder(code, BSType, price, volume, strategyID=0):
    orderfield = tdapi.CThostFtdcInputOrderField()
    orderfield.BrokerID = g.broker_id
    orderfield.ExchangeID = g.ExchangeID[code]
    orderfield.InstrumentID = code
    orderfield.UserID = g.investorID
    orderfield.InvestorID = g.investorID
    orderfield.LimitPrice = price
    orderfield.VolumeTotalOriginal = volume

    # 只有上期所和能源中心有平今平昨，剩下的都是先开先平

    if BSType == 'buyopen':
        # 买卖方向
        orderfield.Direction = tdapi.THOST_FTDC_D_Buy
        # 组合开平标志
        orderfield.CombOffsetFlag = '0'
    elif BSType == 'buyclose':
        orderfield.Direction = tdapi.THOST_FTDC_D_Buy
        orderfield.CombOffsetFlag = '1'
    elif BSType == 'sellopen':
        orderfield.Direction = tdapi.THOST_FTDC_D_Sell
        orderfield.CombOffsetFlag = '0'
    elif BSType == 'sellclose':
        orderfield.Direction = tdapi.THOST_FTDC_D_Sell
        orderfield.CombOffsetFlag = '1'
    elif BSType == 'buyclosetoday':
        orderfield.Direction = tdapi.THOST_FTDC_D_Buy
        orderfield.CombOffsetFlag = tdapi.THOST_FTDC_OF_CloseToday
    elif BSType == 'sellclosetoday':
        orderfield.Direction = tdapi.THOST_FTDC_D_Sell
        orderfield.CombOffsetFlag = tdapi.THOST_FTDC_OF_CloseToday
    else:
        timePrintLog('下单委托类型错误！停止下单！')
        return

    # 当前单号
    orderRef = get_OrderRef()
    orderfield.OrderRef = str(orderRef)

    # 普通限价单的默认参数
    # 报单价格条件
    orderfield.OrderPriceType = tdapi.THOST_FTDC_OPT_LimitPrice
    # 触发条件
    orderfield.ContingentCondition = tdapi.THOST_FTDC_CC_Immediately
    # 有效期类型
    orderfield.TimeCondition = tdapi.THOST_FTDC_TC_GFD
    # 成交量类型
    orderfield.VolumeCondition = tdapi.THOST_FTDC_VC_AV
    # 组合投机套保标志
    orderfield.CombHedgeFlag = "1"
    # GTD日期
    orderfield.GTDDate = ""

    # 最小成交量
    orderfield.MinVolume = 0
    # 强平原因
    orderfield.ForceCloseReason = tdapi.THOST_FTDC_FCC_NotForceClose
    # 自动挂起标志
    orderfield.IsAutoSuspend = 0

    ret = g.tduserapi.ReqOrderInsert(orderfield, 0)
    if ret == 0:
        timePrintLog('下单成功！')
        g.order_map[str(orderRef)] = orderInfo(orderRef, strategyID)
        # 报单回报里的报单价格和品种数据不对，所以自己记录数据
        g.order_map[str(orderRef)].orderPrice = price
        g.order_map[str(orderRef)].instrumentID = code
        # timePrintLog()(g.order_map)
    else:
        timePrintLog('下单失败！')
        judge_ret(ret)
    return ret, orderRef


# 撤单
def cancelOrder(orderRef):
    orderfield = tdapi.CThostFtdcInputOrderActionField()
    orderfield.BrokerID = g.broker_id
    orderfield.UserID = g.investorID
    orderfield.InvestorID = g.investorID

    orderfield.FrontID = g.frontID
    orderfield.SessionID = g.sessionID

    if orderRef in g.order_map.keys():
        orderfield.InstrumentID = g.order_map[str(orderRef)].instrumentID
    else:
        timePrintLog(f'订单{orderRef}不在订单库中，请检查！')
        return -9

    # timePrintLog()(f'g.frontID:{g.frontID}')
    # timePrintLog()(f'g.sessionID:{g.sessionID}')
    # timePrintLog()(f'g.order_map[orderRef].instrumentID:{g.order_map[str(orderRef)].instrumentID}')

    # 当前单号
    orderfield.OrderRef = str(orderRef)

    # 操作标志
    orderfield.ActionFlag = '0'

    ret = g.tduserapi.ReqOrderAction(orderfield, 0)
    if ret == 0:
        timePrintLog('发送撤单成功！')
    else:
        timePrintLog('发送撤单失败！')
        judge_ret(ret)
    return ret


# 判断发送请求失败原因
def judge_ret(ret):
    if ret == -1:
        timePrintLog('失败原因：网络连接失败')
    elif ret == -2:
        timePrintLog('失败原因：表示未处理请求超过许可数')
    elif ret == -3:
        timePrintLog('失败原因：表示每秒发送请求数超过许可数')
    else:
        timePrintLog('失败原因：未知。\nret：{}'.format(ret))


# 获取最大保单引用
def get_OrderRef():
    g.maxOrderRef += 1
    return g.maxOrderRef - 1


# 将数据写入csv中,w代表删除原有的写入，w+代表读写，a代表追写，a+代表读+追写
# 一万行，40列，花费3.1秒
def write_to_csv(file_name, method, content):
    """

    @param file_name:
    @param method: w代表删除原有的写入，w+代表读写，a代表追写，a+代表读+追写
    @param content: 注意！！！，内容为一个列表，即：需要用[]括起来，如['1',str(a)]
    """
    # with open(file_name, 'a') as f:
    # 1. 创建文件对象
    f = open(file_name, method, newline='')
    # 2. 基于文件对象构建 csv写入对象
    csv_writer = csv.writer(f)
    # 4. 写入csv文件内容
    csv_writer.writerow(content)
    # 5. 关闭文件
    f.close()


def get_file_name(path, ext):
    """
    获取指定路径下，指定格式的所有文件名
    @param path: 路径，如：上一层 '../'
    @param ext: 指定后缀
    """
    listallfile = os.listdir(path)
    listfile = []

    # 获取指定格式的文件
    for i in listallfile:
        if ext in i:
            listfile.append(i)
    return listfile[:]


# 创建交易流水
def create_tradeLogFile():
    # 遍历所有策略，将所有策略的合约进行合并
    content = ['自然日', '交易日', '时间', '标的', '方向', '委托价', '成交价', '成交量', '平仓盈亏', '手续费']
    path = './交易流水/'
    for strategy in g.strategy_map.values():
        for subID in strategy.subID:
            file_name = 'strategy{}_{}.csv'.format(strategy.strategyID, subID)
            # 判断文件是否存在
            if file_name not in get_file_name(path, '.csv'):
                write_to_csv(path + file_name, 'w', content)


# 写入交易流水
def writeToTradeLogFile(pTrade):
    # 报单回报里的报单价格数据不对
    # orderPrice = g.order_map[pTrade.OrderRef].pOrder.LimitPrice
    orderPrice = g.order_map[pTrade.OrderRef].orderPrice

    strategyID = g.order_map[pTrade.OrderRef].strategyID
    fee = calculate_Commissionrate(pTrade)
    # 判断交易方向
    dirction = ''
    if pTrade.Direction == '0':
        dirction += '买'
    elif pTrade.Direction == '1':
        dirction += '卖'
    if pTrade.OffsetFlag == '0':
        dirction += '开'
    elif pTrade.OffsetFlag == '1':
        dirction += '平'
    elif pTrade.OffsetFlag == '2':
        dirction += '平（强平）'
    elif pTrade.OffsetFlag == '3':
        dirction += '平（今）'
    elif pTrade.OffsetFlag == '4':
        dirction += '平（昨）'

    # 计算平仓盈亏
    # # 打印逐笔持仓明细
    # timePrintLog()(g.positionDetail_map)
    # for objName in g.positionDetail_map.keys():
    #     timePrintLog()(objName)
    #     print_object(g.positionDetail_map[objName])
    profit = '平仓盈亏'
    # 开仓
    if pTrade.OffsetFlag == '0':
        profit = 0
    # 平仓（包括平仓，平今，平昨等）
    else:
        positionDetail = None
        positionName_list = []
        # 获取该品种的持仓
        for positionName in g.positionDetail_map.keys():
            if pTrade.InstrumentID in positionName:
                positionName_list.append(positionName)
        # 如果是上期所或者能源中心需要判断是平今还是平昨
        if pTrade.ExchangeID == 'SHFE' or pTrade.ExchangeID == 'INE':
            # 平多今
            if pTrade.OffsetFlag == '3' and pTrade.Direction == '1':
                # 获取今日多仓
                for positionName in positionName_list:
                    if '今' in positionName and '多' in positionName:
                        positionDetail = g.positionDetail_map[positionName]
                        break
            # 平空今
            if pTrade.OffsetFlag == '3' and pTrade.Direction == '0':
                # 获取今日持仓
                for positionName in positionName_list:
                    if '今' in positionName and '空' in positionName:
                        positionDetail = g.positionDetail_map[positionName]
                        break
            # 平昨, 多
            elif pTrade.OffsetFlag == '4' and pTrade.Direction == '1':
                # 获取昨日持仓
                for positionName in positionName_list:
                    if '昨' in positionName and '多' in positionName:
                        positionDetail = g.positionDetail_map[positionName]
                        break
            # 平昨， 空
            elif pTrade.OffsetFlag == '4' and pTrade.Direction == '0':
                # 获取昨日持仓
                for positionName in positionName_list:
                    if '昨' in positionName and '空' in positionName:
                        positionDetail = g.positionDetail_map[positionName]
                        break

        # 如果不是上期所或者能源中心
        else:
            # 平多
            if pTrade.Direction == '1':
                # 卖
                for positionName in positionName_list:
                    if '多' in positionName:
                        positionDetail = g.positionDetail_map[positionName]
                        break
            # 平空
            elif pTrade.Direction == '0':
                # 买
                for positionName in positionName_list:
                    if '空' in positionName:
                        positionDetail = g.positionDetail_map[positionName]
                    break
            # positionName = positionName_list[0]
            # positionDetail = g.positionDetail_map[positionName]
        # timePrintLog()(positionName_list)
        # timePrintLog()(positionDetail)
        # timePrintLog()(positionDetail.openPrice_list)
        #
        # timePrintLog()(positionDetail.openPrice_list[0:int(pTrade.Volume)])
        # timePrintLog()(round(float(pTrade.Price), 4))
        # product = del_num(pTrade.InstrumentID)
        # timePrintLog()(float(g.productInfo[product]["合约乘数"]))
        # timePrintLog()(pTrade.Direction)

        # 计算持仓盈亏
        profit = 0
        if pTrade.Direction == '0':
            # dirction += '买'
            product = del_num(pTrade.InstrumentID)
            for openPrice in positionDetail.openPrice_list[0:int(pTrade.Volume)]:
                profit += (openPrice - round(float(pTrade.Price), 4)) * round(float(g.productInfo[product]["合约乘数"]), 1)
        elif pTrade.Direction == '1':
            # dirction += '卖'
            product = del_num(pTrade.InstrumentID)
            for openPrice in positionDetail.openPrice_list[0:int(pTrade.Volume)]:
                profit += (round(float(pTrade.Price), 4) - openPrice) * round(float(g.productInfo[product]["合约乘数"]), 1)
        # 更新持仓
        g.positionDetail_map[positionName].openPrice_list = positionDetail.openPrice_list[int(pTrade.Volume):]
        # timePrintLog()(len(g.positionDetail_map[positionName].openPrice_list))
        # timePrintLog()(profit)

    # timePrintLog()(f'手续费：{fee}')
    content = [pTrade.TradeDate, pTrade.TradingDay, pTrade.TradeTime, pTrade.InstrumentID, dirction,
               orderPrice, pTrade.Price, pTrade.Volume, round(profit, 0), fee]
    write_to_csv('./交易流水/strategy{}_{}.csv'.format(strategyID, pTrade.InstrumentID), 'a', content)

    timePrintLog('写完交易流水')
    # # 打印逐笔持仓明细
    # timePrintLog()(g.positionDetail_map)
    # for objName in g.positionDetail_map.keys():
    #     timePrintLog()(objName)
    #     print_object(g.positionDetail_map[objName])

    # # 全部成交
    # if g.order_map[pTrade.OrderRef].OrderStatus == '0':
    #     del g.order_map[pTrade.OrderRef]


def del_num(content):
    res = re.sub('\d', '', content)
    return res


# 计算手续费
def calculate_Commissionrate(pTrade):
    # 产品
    product = del_num(pTrade.InstrumentID)
    # 数量
    volume = pTrade.Volume
    # 合约乘数
    volumeMultiple = float(g.productInfo[product]["合约乘数"])
    # 开仓手续费率
    OpenRatioByMoney = float(g.productInfo[product]["开仓手续费率"])
    # 开仓手续费
    openRatioByVolume = float(g.productInfo[product]["开仓手续费"])
    # 平仓手续费率
    closeRatioByMoney = float(g.productInfo[product]["平仓手续费率"])
    # 平仓手续费
    closeRatioByVolume = float(g.productInfo[product]["平仓手续费"])
    # 平今手续费率
    closeTodayRatioByMoney = float(g.productInfo[product]["平今手续费率"])
    # 平今手续费
    closeTodayRatioByVolume = float(g.productInfo[product]["平今手续费"])

    fee = '手续费'

    # 这个信号是根据下单来决定的，填的平仓，实际平的是今仓，但是回报里是平仓，会按照平仓进行计算，有的时候会造成错误
    # 比如，m合约，平今手续费0.1，平昨是0.2
    # 开仓
    if pTrade.OffsetFlag == '0':
        fee = volume * (pTrade.Price * volumeMultiple * OpenRatioByMoney + openRatioByVolume)
        pass
    # 平仓
    elif pTrade.OffsetFlag == '1':
        fee = volume * (pTrade.Price * volumeMultiple * closeRatioByMoney + closeRatioByVolume)
        pass
    # 强平
    elif pTrade.OffsetFlag == '2':
        pass
    # 平今
    elif pTrade.OffsetFlag == '3':
        fee = volume * (pTrade.Price * volumeMultiple * closeTodayRatioByMoney + closeTodayRatioByVolume)
        pass
    # 平昨
    elif pTrade.OffsetFlag == '4':
        fee = volume * (pTrade.Price * volumeMultiple * closeRatioByMoney + closeRatioByVolume)
        pass

    return fee


# 更新逐笔持仓明细
def updatePositionDetail(ExchangeID, Direction, InstrumentID, Volume, OpenPrice, OpenDate):
    # 如果不是上期所和能源中心，命名为：au2206_多，
    if ExchangeID != 'SHFE' and ExchangeID != 'INE':
        positionDirection = ''
        if Direction == '0':
            positionDirection = '多'
        elif Direction == '1':
            positionDirection = '空'
        positionDetailName = '{}_{}'.format(InstrumentID, positionDirection)

        # 如果该合约第一次出现，则创建持仓明细类，否则不用，直接添加参数即可
        if positionDetailName not in g.positionDetail_map.keys():
            g.positionDetail_map[positionDetailName] = positionDetailInfo()
        # g.positionDetail_map[positionDetailName].position_list.insert(0, copy.copy(pInvestorPositionDetail))
        # 在开仓价列表中添加开仓价
        for i in range(int(Volume)):
            g.positionDetail_map[positionDetailName].openPrice_list.insert(0, round(OpenPrice, 2))

    # 如果是上期所或者能源中心，命名为：昨_au2206_多
    elif ExchangeID == 'SHFE' or ExchangeID == 'INE':
        positionDirection = ''
        if Direction == '0':
            positionDirection = '多'
        elif Direction == '1':
            positionDirection = '空'
        positionDate = ''
        # 开仓日期指开仓时的交易日期
        if OpenDate == g.tradingDay:
            positionDate = '今'
        elif OpenDate != g.tradingDay:
            positionDate = '昨'
        # timePrintLog()(f'OpenDate:{OpenDate}')
        # timePrintLog()(f'g.tradingDay:{g.tradingDay}')
        positionDetailName = '{}_{}_{}'.format(positionDate, InstrumentID,
                                               positionDirection)
        # 如果该合约第一次出现，则创建持仓明细类，否则不用，之间添加参数即可
        if positionDetailName not in g.positionDetail_map.keys():
            g.positionDetail_map[positionDetailName] = positionDetailInfo()
        # g.positionDetail_map[positionDetailName].position_list.insert(0, copy.copy(pInvestorPositionDetail))
        # timePrintLog()(g.positionDetail_map[positionDetailName].position_list[])
        for i in range(int(Volume)):
            g.positionDetail_map[positionDetailName].openPrice_list.insert(0, round(OpenPrice, 2))


# 更新今天逐笔持仓明细
def updateTodayPositionDetail(ExchangeID, Direction, OffsetFlag, InstrumentID, Volume, OpenPrice, OpenDate):
    if OffsetFlag != '0':
        return
    # 如果不是上期所和能源中心，命名为：au2206_多，
    if ExchangeID != 'SHFE' and ExchangeID != 'INE':
        positionDirection = ''
        if Direction == '0':
            positionDirection = '多'
        elif Direction == '1':
            positionDirection = '空'
        positionDetailName = '{}_{}'.format(InstrumentID, positionDirection)

        # 如果该合约第一次出现，则创建持仓明细类，否则不用，直接添加参数即可
        if positionDetailName not in g.positionDetail_map.keys():
            g.positionDetail_map[positionDetailName] = positionDetailInfo()
        # g.positionDetail_map[positionDetailName].position_list.insert(0, copy.copy(pInvestorPositionDetail))
        # 在开仓价列表最后添加开仓价
        for i in range(int(Volume)):
            g.positionDetail_map[positionDetailName].openPrice_list.append(round(OpenPrice, 2))

    # 如果是上期所或者能源中心，命名为：昨_au2206_多
    elif ExchangeID == 'SHFE' or ExchangeID == 'INE':
        positionDirection = ''
        if Direction == '0':
            positionDirection = '多'
        elif Direction == '1':
            positionDirection = '空'
        positionDate = ''
        # 开仓日期指开仓时的交易日期
        if OpenDate == g.tradingDay:
            positionDate = '今'
        elif OpenDate != g.tradingDay:
            positionDate = '昨'
        # timePrintLog()(f'OpenDate:{OpenDate}')
        # timePrintLog()(f'g.tradingDay:{g.tradingDay}')
        positionDetailName = '{}_{}_{}'.format(positionDate, InstrumentID, positionDirection)
        # 如果该合约第一次出现，则创建持仓明细类，否则不用，之间添加参数即可
        if positionDetailName not in g.positionDetail_map.keys():
            g.positionDetail_map[positionDetailName] = positionDetailInfo()
        # g.positionDetail_map[positionDetailName].position_list.insert(0, copy.copy(pInvestorPositionDetail))
        # timePrintLog()(g.positionDetail_map[positionDetailName].position_list[])
        for i in range(int(Volume)):
            g.positionDetail_map[positionDetailName].openPrice_list.append(round(OpenPrice, 2))


# 记录资源占用情况
def process_monitor():
    if not isFileIn('{}.csv'.format(datetime.datetime.now().strftime("%Y%m%d")), './资源占用'):
        initProcessMonitor()

    cpu_percent = round(g.process.cpu_percent(None), 2)
    mem_percent = round(g.process.memory_percent(), 2)
    now_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
    mem = round(g.process.memory_info().rss / 1024 / 1024, 2)
    content = [now_time, cpu_percent, mem, mem_percent]
    write_to_csv('./资源占用/{}.csv'.format(datetime.datetime.now().strftime("%Y%m%d")), 'a', content)


def initProcessMonitor():
    content = ['当前时间', 'CPU使用（%）', '内存使用（MB）', '内存占用（%）']
    write_to_csv('./资源占用/{}.csv'.format(datetime.datetime.now().strftime("%Y%m%d")), 'w', content)
    pid = os.getpid()
    g.process = psutil.Process(pid)


def make_dir(path, dir_name):
    if path[-1] != '/':
        path += '/'
    all_file_name = os.listdir(path)
    if dir_name in all_file_name:
        timePrintLog('{}已存在，无需创建'.format(dir_name))
        return -1
    else:
        os.mkdir(path + dir_name)
        timePrintLog('{}已创建'.format(dir_name))
        return 0


# 判断当前时间是否是规定时间

def is_now(set_now, time_level):
    """

    Parameters
    ----------
    set_now 设定的时间
    time_level 判断日期，小时，分钟，还是秒

    Returns
    -------
    例如：
    result = is_now('10:33:30', 'second')
    result = is_now('2021.12.10', 'day')
    """
    ts_set = pd.Timestamp(set_now)
    ts_now = pd.Timestamp(datetime.datetime.now())
    if time_level == 'day':
        if ts_set.year == ts_now.year and ts_set.month == ts_now.month and ts_set.day == ts_now.day:
            return True
        else:
            return False
    elif time_level == 'hour':
        if ts_set.hour == ts_now.hour:
            return True
        else:
            return False
    elif time_level == 'minute':
        if ts_set.hour == ts_now.hour and ts_set.minute == ts_now.minute:
            return True
        else:
            return False
    elif time_level == 'second':
        if ts_set.hour == ts_now.hour and ts_set.minute == ts_now.minute and ts_set.second == ts_now.second:
            return True
        else:
            return False
    elif time_level == 'year':
        if ts_set.year == ts_now.year:
            return True
        else:
            return False

# 发送到百度云，remotepath = '期货tick数据'（固定格式，不能加./）
def uploadToBaiDuYun(file_name, remotepath):
    # 获取一个bypy对象，封装了所有百度云文件操作的方法
    bp = ByPy()

    # 百度网盘创建远程文件夹
    # bp.mkdir(remotepath='dir_name')

    # 上传某一文件到百度云网盘对应的远程文件夹
    # ondup中参数代表复制文件，默认值为'overwrite'，指定'newcopy'不会覆盖重复文件
    ret = bp.upload(localpath=file_name, remotepath=remotepath, ondup='newcopy')
    # timePrintLog()(ret)
    # if ret == 0:
    #     timePrintLog()('发送成功')
    # else:
    #     timePrintLog()('发送失败')
    #     timePrintLog()('失败原因是：{}'.format(ret))

    return ret


def getZipDir(dirpath, outFullName):
    """
    压缩指定文件夹
    :param dirpath: 目标文件夹路径
    :param outFullName: 压缩文件保存路径+xxxx.zip
    :return: 无
    """
    zip = zipfile.ZipFile(outFullName, "w", zipfile.ZIP_DEFLATED)
    for path, dirnames, filenames in os.walk(dirpath):
        # 去掉目标跟路径，只对目标文件夹下边的文件及文件夹进行压缩
        fpath = path.replace(dirpath, '')

        for filename in filenames:
            zip.write(os.path.join(path, filename), os.path.join(fpath, filename))
    zip.close()


# 判断是否开盘
def is_open():
    ts.set_token(str(g.tushareToken))
    pro = ts.pro_api()
    today = datetime.datetime.now().strftime('%Y%m%d')
    df = pro.trade_cal(exchange='SSE', is_open='1', start_date='20220101', end_date=today, fields='cal_date')
    if today in list(df['cal_date']):
        return True
    else:
        return False


# 判断文件是否存在
def isFileIn(file_name, path):
    # all_file_name = os.listdir(path)
    if file_name in os.listdir(path):
        # timePrintLog()('{}存在'.format(file_name))
        return True
    else:
        # timePrintLog()('{}不存在'.format(file_name))
        return False


# 读取json文件示例
def read_json():
    with open('./con_file/account.json', 'r', encoding='utf8') as f:
        account = json.load(f)

    timePrintLog(account["broker_name"])


# 读取ini文件示例
def read_ini():
    account = configparser.ConfigParser()
    account.read('./con_file/account.ini', encoding='utf-8')

    timePrintLog(account.get('account', 'broker_name'))


# 计算函数运行时间
def jisuan():
    star = time.time()

    try:
        for i in range(100000):
            write_to_csv('./测试/1.csv', 'w', [1135465] * 40)
    except Exception as e:
        red_printLog(e)

    end = time.time()
    timePrintLog('time:{}'.format(end - star))

# 检测zip是否存在且没有问题
def checkZip(fileName):
    # 使用方法
    # if not checkZip(file_name):
    #     print('no')
    # else:
    #     print('yes')

    try:
        zf = zipfile.ZipFile(fileName)
        zf.testzip()
    except Exception as e:
        print(e)
        return False
    else:
        return True
